// Copyright (c) ZeroC, Inc.

package com.zeroc.IceGridGUI.LiveDeployment;

import com.jgoodies.looks.BorderStyle;
import com.jgoodies.looks.HeaderStyle;
import com.jgoodies.looks.Options;
import com.jgoodies.looks.plastic.PlasticLookAndFeel;
import com.zeroc.IceGrid.*;
import com.zeroc.IceGridGUI.*;
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.util.prefs.Preferences;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ButtonGroup;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JOptionPane;
import javax.swing.JRadioButtonMenuItem;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JToggleButton;
import javax.swing.JToolBar;
import javax.swing.KeyStroke;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;

class ShowLogFileDialog extends JDialog {
    static interface FileIteratorFactory {
        FileIteratorPrx open(int count) throws com.zeroc.Ice.UserException;

        String getTitle();

        String getDefaultFilename();
    }

    private class FIFOTextArea extends JTextArea {
        FIFOTextArea(int rows, int columns) {
            super(rows, columns);
            setEditable(false);
            setLineWrap(true);
        }

        void appendLines(final String[] lines, final int maxLines, final int maxSize) {
            SwingUtilities.invokeLater(
                    () -> {
                        for (int i = 0; i < lines.length; ++i) {
                            // The last line is always incomplete
                            if (i + 1 != lines.length) {
                                append(lines[i] + "\n");
                            } else {
                                append(lines[i]);
                            }
                            removeLines(maxLines, maxSize);
                        }
                    });
        }

        void removeLines(int maxLines, int maxSize) {
            javax.swing.text.Document doc = getDocument();
            javax.swing.text.Element rootElt = doc.getDefaultRootElement();

            // We keep at least one line, no matter its length
            int lineCount = getLineCount();
            while (lineCount > 1 && (doc.getLength() > maxSize || (lineCount > maxLines))) {
                javax.swing.text.Element firstLine = rootElt.getElement(0);
                try {
                    doc.remove(0, firstLine.getEndOffset());
                } catch (javax.swing.text.BadLocationException ble) {
                    assert false;
                }
                lineCount--;
            }
            setCaretPosition(doc.getLength());
        }
    }

    private class ReaderThread extends Thread {
        ReaderThread() {
            _threadMaxLines = _maxLines;
            _threadMaxSize = _maxSize;
            _threadInitialLines = _initialLines;
            _threadMaxReadSize = _maxReadSize;
            _threadPeriod = _period;

            _playButton.setSelected(true);
            _playItem.setSelected(true);
            _pause.setEnabled(true);
        }

        private void openError(final String message) {
            SwingUtilities.invokeLater(
                    () -> {
                        if (_textArea.getText() == null || _textArea.getText().isEmpty()) {
                            close(true);
                        } else {
                            stopReading();
                        }

                        JOptionPane.showMessageDialog(
                                ShowLogFileDialog.this,
                                message,
                                _factory.getTitle() + ": cannot open file",
                                JOptionPane.ERROR_MESSAGE);
                    });
        }

        @Override
        public void run() {
            // Open file
            int initialLines;

            synchronized (this) {
                initialLines = _threadInitialLines;
            }

            try {
                _p = _factory.open(initialLines);
            } catch (com.zeroc.Ice.UserException e) {
                openError(e.toString());
                return;
            } catch (com.zeroc.Ice.LocalException e) {
                openError(e.toString());
                return;
            }

            SwingUtilities.invokeLater(
                    () -> {
                        if (isVisible()) {
                            _textArea.setText(null);
                        } else {
                            setVisible(true);
                        }
                    });

            boolean firstRun = true;
            for (; ; ) {
                synchronized (this) {
                    if (!_done) {
                        if (_paused) {
                            while (_paused && !_done) {
                                try {
                                    wait();
                                } catch (InterruptedException e) {
                                }
                            }
                        } else if (!firstRun) {
                            try {
                                wait(_threadPeriod);
                            } catch (InterruptedException e) {
                            }
                        } else {
                            firstRun = false;
                        }
                    }

                    if (_done) {
                        cleanupIterator();
                        return;
                    }
                }

                boolean eofEncountered = false;

                while (!eofEncountered) {
                    int maxLines;
                    int maxSize;
                    int maxReadSize;

                    synchronized (this) {
                        if (_done || _paused) {
                            break; // while(!eofEncountered)
                        }

                        maxLines = _threadMaxLines;
                        maxSize = _threadMaxSize;
                        maxReadSize = _threadMaxReadSize;
                    }

                    FileIterator.ReadResult r = null;

                    try {
                        r = _p.read(maxReadSize);
                        eofEncountered = r.returnValue;
                    } catch (com.zeroc.IceGrid.FileNotAvailableException e) {
                        _textArea.appendLines(
                                new String[] {
                                    "---------------------------",
                                    "IceGrid GUI caught: " + e.toString(),
                                    "---------------------------"
                                },
                                maxLines,
                                maxSize);
                        SwingUtilities.invokeLater(() -> stopReading());
                        cleanupIterator();
                        return;
                    } catch (com.zeroc.Ice.LocalException e) {
                        _textArea.appendLines(
                                new String[] {
                                    "---------------------------",
                                    "IceGrid GUI caught: " + e.toString(),
                                    "---------------------------"
                                },
                                maxLines,
                                maxSize);
                        SwingUtilities.invokeLater(() -> stopReading());
                        return;
                    }

                    _textArea.appendLines(r.lines, maxLines, maxSize);
                }
            }
        }

        private void cleanupIterator() {
            try {
                _p.destroy();
            } catch (com.zeroc.Ice.LocalException e) {
                // Ignored, maybe should log warning
            }
        }

        synchronized void pause() {
            if (!_paused) {
                _paused = true;
                notify();
            }
        }

        synchronized void terminate() {
            if (!_done) {
                _done = true;
                notify();
            }
        }

        synchronized void play() {
            if (_paused) {
                _paused = false;
                notify();
            }
        }

        synchronized void setPrefs() {
            _threadMaxLines = _maxLines;
            _threadMaxSize = _maxSize;
            _threadInitialLines = _initialLines;
            _threadMaxReadSize = _maxReadSize;
            _threadPeriod = _period;
        }

        private FileIteratorPrx _p;
        private boolean _done = false;
        private boolean _paused = false;

        private int _threadMaxLines;
        private int _threadMaxSize;
        private int _threadInitialLines;
        private int _threadMaxReadSize;
        private int _threadPeriod;
    }

    private class MenuBar extends JMenuBar {
        private MenuBar() {
            putClientProperty(Options.HEADER_STYLE_KEY, HeaderStyle.BOTH);
            putClientProperty(PlasticLookAndFeel.BORDER_STYLE_KEY, BorderStyle.SEPARATOR);

            final int MENU_MASK = java.awt.Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();

            // File menu
            JMenu fileMenu = new JMenu("File");
            fileMenu.setMnemonic(java.awt.event.KeyEvent.VK_F);
            add(fileMenu);

            ButtonGroup bg = new ButtonGroup();

            _pauseItem = new JRadioButtonMenuItem(_pause);
            fileMenu.add(_pauseItem);
            bg.add(_pauseItem);
            _playItem = new JRadioButtonMenuItem(_play);
            fileMenu.add(_playItem);
            bg.add(_playItem);
            _stopItem = new JRadioButtonMenuItem(_stop);
            fileMenu.add(_stopItem);
            bg.add(_stopItem);
            fileMenu.addSeparator();

            Action save =
                    new AbstractAction("Save As...") {
                        @Override
                        public void actionPerformed(ActionEvent e) {
                            JFileChooser fileChooser =
                                    _root.getCoordinator().getSaveLogFileChooser();

                            fileChooser.setSelectedFile(
                                    new java.io.File(
                                            fileChooser.getCurrentDirectory(),
                                            _factory.getDefaultFilename()));

                            java.io.File file = null;

                            while (file == null) {
                                int result = fileChooser.showSaveDialog(ShowLogFileDialog.this);
                                if (result == JFileChooser.APPROVE_OPTION) {
                                    file = fileChooser.getSelectedFile();

                                    if (file != null) {
                                        if (!file.exists() && file.getName().indexOf('.') == -1) {
                                            file =
                                                    new java.io.File(
                                                            file.getAbsolutePath() + ".log");
                                        }

                                        java.io.OutputStreamWriter os = null;

                                        try {
                                            os =
                                                    new java.io.OutputStreamWriter(
                                                            new java.io.FileOutputStream(file));
                                            String txt = _textArea.getText();
                                            if (txt == null) {
                                                txt = "";
                                            }
                                            os.write(txt, 0, txt.length());
                                        } catch (java.io.IOException io) {
                                            JOptionPane.showMessageDialog(
                                                    ShowLogFileDialog.this,
                                                    io.toString(),
                                                    "Cannot write file",
                                                    JOptionPane.ERROR_MESSAGE);
                                        } finally {
                                            if (os != null) {
                                                try {
                                                    os.close();
                                                } catch (java.io.IOException io) {
                                                }
                                            }
                                        }
                                    }
                                } else {
                                    break; // while
                                }
                            }
                        }
                    };
            save.putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_S, MENU_MASK));
            save.putValue(Action.SHORT_DESCRIPTION, "Save As...");
            fileMenu.add(save);
            fileMenu.addSeparator();

            fileMenu.add(
                    new AbstractAction("Close") {
                        @Override
                        public void actionPerformed(ActionEvent e) {
                            close(true);
                        }
                    });
            JMenu editMenu = new JMenu("Edit");
            editMenu.setMnemonic(java.awt.event.KeyEvent.VK_E);
            add(editMenu);

            Action copy =
                    new AbstractAction("Copy") {
                        @Override
                        public void actionPerformed(ActionEvent e) {
                            _textArea.copy();
                        }
                    };
            copy.putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_C, MENU_MASK));
            copy.putValue(Action.SHORT_DESCRIPTION, "Copy");
            editMenu.add(copy);

            editMenu.addSeparator();
            Action selectAll =
                    new AbstractAction("Select All") {
                        @Override
                        public void actionPerformed(ActionEvent e) {
                            _textArea.grabFocus();
                            _textArea.selectAll();
                        }
                    };
            selectAll.putValue(
                    Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_A, MENU_MASK));
            selectAll.putValue(Action.SHORT_DESCRIPTION, "Select All");

            editMenu.add(selectAll);
            editMenu.addSeparator();
            editMenu.add(
                    new AbstractAction("Preferences...") {
                        @Override
                        public void actionPerformed(ActionEvent e) {
                            new LogPrefsDialog(ShowLogFileDialog.this);
                        }
                    });
        }
    }

    private class ToolBar extends JToolBar {
        private ToolBar() {
            putClientProperty(Options.HEADER_STYLE_KEY, HeaderStyle.BOTH);
            putClientProperty(PlasticLookAndFeel.BORDER_STYLE_KEY, BorderStyle.SEPARATOR);
            setFloatable(false);
            putClientProperty("JToolBar.isRollover", Boolean.TRUE);

            _pauseButton = new JToggleButton(_pause);
            _pauseButton.setText(null);
            _pauseButton.setIcon(Utils.getIcon("/icons/16x16/pause.png"));
            add(_pauseButton);
            _playButton = new JToggleButton(_play);
            _playButton.setText(null);
            _playButton.setIcon(Utils.getIcon("/icons/16x16/start.png"));
            add(_playButton);
            _stopButton = new JToggleButton(_stop);
            _stopButton.setText(null);
            _stopButton.setIcon(Utils.getIcon("/icons/16x16/stop.png"));
            add(_stopButton);

            ButtonGroup bg = new ButtonGroup();
            bg.add(_pauseButton);
            bg.add(_playButton);
            bg.add(_stopButton);
        }
    }

    ShowLogFileDialog(
            Root root,
            FileIteratorFactory factory,
            int maxLines,
            int maxSize,
            int initialLines,
            int maxReadSize,
            int period) {
        super(root.getCoordinator().getMainFrame(), factory.getTitle() + " - IceGrid GUI", false);

        _maxLines = maxLines;
        _maxSize = maxSize;
        _initialLines = initialLines;
        _maxReadSize = maxReadSize;
        _period = period;
        _factory = factory;
        _root = root;
        _preferences = Coordinator.getPreferences().node("LiveDeployment");

        setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
        addWindowListener(
                new java.awt.event.WindowAdapter() {
                    @Override
                    public void windowClosing(java.awt.event.WindowEvent e) {
                        close(true);
                    }
                });

        _pause =
                new AbstractAction("Pause") {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        pause();
                    }
                };

        _play =
                new AbstractAction("Play") {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        play();
                    }
                };

        _stop =
                new AbstractAction("Stop") {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        stopReading();
                    }
                };

        setJMenuBar(new MenuBar());
        getContentPane().add(new ToolBar(), BorderLayout.PAGE_START);

        JScrollPane scrollPane =
                new JScrollPane(
                        _textArea,
                        ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
                        ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);

        getContentPane().add(scrollPane);

        setResizable(true);
        pack();
        Utils.restoreWindowBounds(
                this, _preferences, "LogFileDialog", _root.getCoordinator().getMainFrame());

        play();
    }

    void pause() {
        _thread.pause();
        _pauseItem.setSelected(true);
        _pauseButton.setSelected(true);
    }

    void stopReading() {
        if (_thread != null) {
            _thread.terminate();

            try {
                _thread.join();
            } catch (InterruptedException e) {
            }

            _thread = null;
            _stopItem.setSelected(true);
            _stopButton.setSelected(true);
            _pause.setEnabled(false);
        }
    }

    void play() {
        if (_thread != null) {
            _thread.play();
            _playItem.setSelected(true);
            _playButton.setSelected(true);
            _pause.setEnabled(true);
        } else {
            _thread = new ReaderThread();
            _thread.start();
        }
    }

    int getMaxLines() {
        return _maxLines;
    }

    int getMaxSize() {
        return _maxSize;
    }

    int getInitialLines() {
        return _initialLines;
    }

    int getMaxReadSize() {
        return _maxReadSize;
    }

    int getPeriod() {
        return _period;
    }

    void setPrefs(int maxLines, int maxSize, int initialLines, int maxReadSize, int period) {
        if (maxLines < 50) {
            maxLines = 50;
        }
        _maxLines = maxLines;

        if (maxSize < 1000) {
            maxSize = 1000;
        }
        _maxSize = maxSize;

        _initialLines = initialLines;

        if (maxReadSize < 100) {
            maxReadSize = 100;
        } else if (maxReadSize + 512 > _root.getMessageSizeMax()) {
            maxReadSize = _root.getMessageSizeMax() - 512;
        }
        _maxReadSize = maxReadSize;

        if (period < 200) {
            period = 200;
        } else if (period > 5000) {
            period = 5000;
        }
        _period = period;

        if (_thread != null) {
            _thread.setPrefs();
        }

        _root.setLogPrefs(_maxLines, _maxSize, _initialLines, _maxReadSize, _period);
    }

    void close(boolean notifyRoot) {
        stopReading();

        if (notifyRoot) {
            _root.removeShowLogFileDialog(_factory.getTitle());
        }

        Utils.storeWindowBounds(this, _preferences.node("LogFileDialog"));

        dispose();
    }

    private final Root _root;
    private final FileIteratorFactory _factory;

    private int _maxLines;
    private int _maxSize;
    private int _initialLines;
    private int _maxReadSize;
    private int _period;

    private Action _play;
    private Action _pause;
    private Action _stop;

    private JRadioButtonMenuItem _playItem;
    private JRadioButtonMenuItem _pauseItem;
    private JRadioButtonMenuItem _stopItem;

    private JToggleButton _playButton;
    private JToggleButton _pauseButton;
    private JToggleButton _stopButton;

    private FIFOTextArea _textArea = new FIFOTextArea(20, 45);
    private ReaderThread _thread;

    private final Preferences _preferences;
}
